# Script que faz o download HTTP do arquivo .zip com a BIOS

# Este script utiliza o web service do dynboot para obter o arquivo da BIOS a ser
# baixado e também para obter a hora local do servidor. O arquivo netclock.nsh é gerado com
# a data e hora do servidor dynboot e se invocado no shell uefi, irá configurar o relógio.
# As URLs dos web services estão no arquivo URLs.py.

# Caso a execução seja feita com sucesso, o exit code será 0

import urllib2
import sys
import string
import os
import zipfile
import email.utils as eut
import time
import io

sys.path.append(os.path.dirname(__file__))
from URLs import *

if (len(sys.argv) != 4):
	print "DownloadBIOS.py - Usage: DownloadBIOS.py NS EnderecoServidorHTTP PastaDestino"
	sys.exit(-1)

def GetXmlTagText(XML, TAG):
	startTag = XML.find('<'+TAG+'>')
	if (startTag > 0) :
		startTag += len(TAG)+2
		endTag = XML.find('</'+TAG+'>')
		if (endTag > 0) :
			return XML[startTag:endTag].strip()
	return ''

def MIIConsultaOP(NS):
	site = URLs[os.environ["LOCATION"]]['ArquivoRequest']
	login = 'WSAUTOMPLC'
	password = 'wsplc1'
	
	requestXml = string.Template("""<?xml version="1.0" encoding="utf-8"?>
	<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xmii="http://www.sap.com/xMII">
		<soapenv:Header/>
		<soapenv:Body>
			<xmii:XacuteRequest>
				<xmii:LoginName>$LOGIN</xmii:LoginName>
				<xmii:LoginPassword>$PASSWORD</xmii:LoginPassword>
				<xmii:InputParams>
					<xmii:CD_NUMER_SERIE>$OP</xmii:CD_NUMER_SERIE>
				</xmii:InputParams>
			</xmii:XacuteRequest>
		</soapenv:Body>
	</soapenv:Envelope>""").substitute({'LOGIN': login, 'PASSWORD': password, 'OP' : OP})

	headers = {
		 'Content-Type': 'text/xml; charset=utf-8',
		 'Content-Length': len(requestXml),
		 'SOAPAction': site
	}

	OP = {}
	try:
		req = urllib2.Request(site, requestXml, headers)
		response = urllib2.urlopen(req)
		responseXML = response.read()
		response.close()
		
		OP['CD_BIOS'] = GetXmlTagText(responseXML, 'CD_BIOS').lstrip('0')
		OP['DS_BIOS'] = GetXmlTagText(responseXML, 'DS_BIOS')
		
	except:
		return OP
		
	return OP
	
# Call the dynboot horaRequest WebService to get the server date and time.
# Return a dictionary with year, month, day, hour, minute and second items or null if failed
def DynbootGetDateTime():
	calendar = {}
	site = URLs[os.environ["LOCATION"]]['dynBoot']
	requestXml = """<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ws="ws:dynboot">\r\n
	   <soapenv:Header/>\r\n
	   <soapenv:Body>\r\n
		  <ws:horaRequest>\r\n
			 <pattern>0</pattern>\r\n
		  </ws:horaRequest>\r\n
	   </soapenv:Body>\r\n
	</soapenv:Envelope>"""

	headers = {
		 'Content-Type': 'text/xml; charset=utf-8',
		 'Content-Length': len(requestXml),
		 'SOAPAction': site
	}

	resultXml = ""
	try:
		req = urllib2.Request(site, requestXml, headers)
		response = urllib2.urlopen(req)
		resultXml = response.read()
		response.close()
		
		if (resultXml.find('<status>S</status>') > 0):
			datetimeString = GetXmlTagText(resultXml, 'hora')
			print datetimeString
			if (len(datetimeString) == 19):
				calendar['day'] = int(datetimeString[0:2])
				calendar['month'] = int(datetimeString[3:5])
				calendar['year'] = int(datetimeString[6:10])
				calendar['hour'] = int(datetimeString[11:13])
				calendar['minute'] = int(datetimeString[14:16])
				calendar['second'] = int(datetimeString[17:19])
		else:
			serverDate = response.headers["Date"]
			date = time.mktime(eut.parsedate(serverDate.strip())) - 3 * 60 * 60
			tm = time.localtime(date)
			print tm
			calendar['day'] = tm.tm_mday
			calendar['month'] = tm.tm_mon
			calendar['year'] = tm.tm_year
			calendar['hour'] = tm.tm_hour
			calendar['minute'] = tm.tm_min
			calendar['second'] = tm.tm_sec
	except:
		return {}
	return calendar

# Given a NS, call Dynboot webservice to get the BIOS file from BIOS Web App
def DynbootArquivoRequest(NS):
	site = URLs[os.environ["LOCATION"]]['dynBoot']
	
	requestXml = string.Template("""<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ws="ws:dynboot">\r\n
	   <soapenv:Header/>\r\n
	   <soapenv:Body>\r\n
		  <ws:arquivoRequest>\r\n
			 <numeroSerie>$NS</numeroSerie>\r\n
			 <tipoArquivo>BIOS</tipoArquivo>\r\n
		  </ws:arquivoRequest>\r\n
	   </soapenv:Body>\r\n
	</soapenv:Envelope>""").substitute({'NS': NS})

	headers = {
		 'Content-Type': 'text/xml; charset=utf-8',
		 'Content-Length': len(requestXml),
		 'SOAPAction': site
	}

	BIOSzipfile = ""
	try:
		req = urllib2.Request(site, requestXml, headers)
		response = urllib2.urlopen(req)
		responseXML = response.read()
		response.close()
		
		BIOSzipfile = GetXmlTagText(responseXML, 'arquivo')
		
		# extract file name from the grub boot menu that is returned by the WebService
		startTag = BIOSzipfile.find('initrd=')
		if (startTag > 0) :
			startTag += 7
			endTag = BIOSzipfile.find(' NumSerie=')
			if (endTag > 0) :
				BIOSzipfile = BIOSzipfile[startTag:endTag].strip()
			else:
				BIOSzipfile = ""
		else: BIOSzipfile = ""
		
	except Exception, e:
		print "DownloadBIOS.py - Exception calling dynboot webservice:\r\n"
		print e
		BIOSzipfile = ""
		
	return BIOSzipfile
	
def Deltree(folder):
	if (os.path.exists(folder)):
		for the_file in os.listdir(folder):
			file_path = os.path.join(folder)
			try:
				os.unlink(file_path)
			except Exception, e:
				continue

def DownloadBiosZipAndExtract(fileurl, destino, destinounzip):
	response = ''
	try:		
		# primeiro, solicita um HTTP Head para obter dados do arquivo de bios.
		# se for o mesmo que ja está no pendrive, não precisa baixar novamente!
		request = urllib2.Request(fileurl)
		request.get_method = lambda : 'HEAD'
		response = urllib2.urlopen(request)
		
		# toda essa mágica é para tirar o último item do array (DST) e setá-lo como 0 invés de -1
		lastmodifiedStruct = list(eut.parsedate(response.headers["Last-Modified"].strip())[0:8])
		lastmodifiedStruct.append(0)
		lastmodified = time.mktime(tuple(lastmodifiedStruct))
		filesize = int(response.headers["Content-Length"].strip())
		response.close()
		
		needDownload = 1
		# verifica necessidade do download
		if (os.path.exists(destino)) :
			st = os.stat(destino)
			locallastmodified = st.st_mtime
			if (filesize == st.st_size and lastmodified == locallastmodified) :
				needDownload = 0
		
		if (needDownload) :
			print 'DownloadBIOS.py - Downloading: %s'%fileurl
			response = urllib2.urlopen(fileurl)		
			
			CHUNK = 16 * 1024
			
			totaldownloaded = 0
			with open(destino, 'wb') as fp:
				while True:
					chunk = response.read(CHUNK)
					if not chunk: break
					fp.write(chunk)
					totaldownloaded += len(chunk)
					print "DownloadBIOS.py - Progresso: %d%%\r"%((totaldownloaded * 100) / filesize), # do not line feed

			print 'DownloadBIOS.py - Progresso: 100%'
			
			# A libc do UEFI está convertendo a data de UTC para local antes de setar no sistema de arquivos!
			# APIs de tempo em arquivos devem considerar que os tempos informados estão em UTC!
			# Então modificamos a data para que quando utime converter para local fique igual ao UTC do servidor web.
			if (os.name == 'edk2') :
				localdummytime = time.mktime(time.localtime(1386196341L))
				gmtdummytime = time.mktime(time.gmtime(1386196341L))
				lastmodified = lastmodified - (gmtdummytime - localdummytime)
			
			# seta data de modificação do arquivo baixado para ser a mesma do servidor			
			os.utime(destino, (lastmodified, lastmodified))
			
			# apaga pasta bioswriter que irá 
			# receber o conteúdo do arquivo .zip com o
			# gravador de bios e NS
			Deltree(destinounzip)
			
			print 'DownloadBIOS.py - Extraindo arquivos... '
			sys.stdout.flush()
			
			with zipfile.ZipFile(destino, 'r') as bioszip:
				bioszip.extractall(destinounzip)
			print 'DownloadBIOS.py - OK'
		else :
			print 'DownloadBIOS.py - Arquivos de BIOS estao atualizados!'
			
	except Exception, e:
		if (os.path.exists(destino)) :
			os.unlink(destino)
		Deltree(destinounzip)
		
		print "DownloadBIOS.py - Erro extraindo arquivos!"
		print e
		return 0
		
	finally:
		if (response) : response.close()
	
	return 1

try:
	#'1A998RF2G' 
	NS = sys.argv[1]
	print 'DownloadBIOS.py - Verificando arquivo de BIOS no dynboot NS: %s'%NS
	biosZip = DynbootArquivoRequest (NS)
	if (not biosZip):
		print "DownloadBIOS.py - BIOS nao encontrada no dynboot para o numero de serie %s!"%NS
		sys.exit(-1)
	print 'DownloadBIOS.py - Dynboot retornou arquivo %s'%biosZip
	
	biosZipUrl = 'http://'
	biosZipUrl += sys.argv[2]
	biosZipUrl += '/'
	biosZipUrl += biosZip
	
	print 'DownloadBIOS.py - Baixando: %s'%biosZipUrl
	print 'DownloadBIOS.py - Destino:  %s'%(sys.argv[3] + "bios.zip")
		
	if (not DownloadBiosZipAndExtract (biosZipUrl, sys.argv[3] + "bios.zip", sys.argv[3])):
		print "DownloadBIOS.py - Erro fazendo download do arquivo de BIOS %s"%biosZip
		sys.exit(-1)
	
	print 'DownloadBIOS.py - Obtendo horario do servidor dynboot.'
	calendar = DynbootGetDateTime()
	if (not calendar):
		print "DownloadBIOS.py - Erro obtendo relogio do servico dynboot."
		sys.exit(-1)
	
	with open(sys.argv[3] + 'netclock.nsh', 'w') as f:
		f.write('date %02d/%02d/%04d\r\n' % (calendar['month'], calendar['day'], calendar['year']))
		f.write('time %02d:%02d:%02d' % (calendar['hour'], calendar['minute'], calendar['second']))
			
except Exception, e:
	print "DownloadBIOS.py - Erro processo de download da BIOS:"
	print e
	sys.exit(-1)
